/* -*- mode: C++; tab-width:4; c-basic-offset: 4; indent-tabs-mode:nil -*- */
#ifndef _SIUT_SIMD_FRAMES_HPP_
#define _SIUT_SIMD_FRAMES_HPP_

#include "standard/Mat4f.hpp"
#include "QuatOperations.hpp"
#include "Vec2f.hpp"
#include "Vec3f.hpp"
#include "Vec4f.hpp"
#include "Ray3f.hpp"
#include "Frustum.hpp"

namespace siut {
namespace simd {
/** \brief matrix and vector calculation tool using standard C++ language */
  
    /** Convert a 3D Euclidean coordinate to a homogenenous point.
     *
     * \author Christopher Dyken <christopher.dyken@sintef.no>
     */
    inline Vec4f
    asHomogeneous( const Vec3f& p )
    {
        return Vec4f( p[0], p[1], p[2], 1.0f );
    }

    /** Project a homogoeneous point into Euclidean 3D space.
     *
     * \author Christopher Dyken <christopher.dyken@sintef.no>
     */
    inline Vec3f
    asEuclidean( const Vec4f& p )
    {
        float s = 1.0f/p[3];
        return Vec3f( s*p[0], s*p[1], s*p[2] );
    }

    /** Transform and project an object space coordinate to normalized device coordinates.
     *
     * \param mvp  The modelview-projection matrix.
     * \param p    The object space coordinate to transform.
     * \returns    The normalized device coordinate [0,1]x[0,1]x[0,1].
     * 
     * \author Christopher Dyken <christopher.dyken@sintef.no>
     */
    inline Vec3f
    objectCoordsAsNormalizedDeviceCoord( const Mat4f& mvp, const Vec3f& p )
    {
        return asEuclidean( mvp * asHomogeneous( p ) );
    }
    
    /** Transform a coordinate in normalized device coordinates to object space.
     *
     * \param mvp_inv  The inverse of the modelview-projection matrix.
     * \param p        The normalized device coordinate point to transform [0,1]x[0,1]x[0,1].
     * \returns        The object space coordinate.
     *
     * \author Christopher Dyken <christopher.dyken@sintef.no>
     */
    inline Vec3f
    normalizedDeviceCoordsAsObjectCoords( const Mat4f& mvp_inv, const Vec3f& p )
    {
        return asEuclidean( mvp_inv * asHomogeneous( p ) );
    }

    /** Transform a mouse position to object space using a plane of interest
     *
     * This function takes a mouse position (normalized to
     * [0,1]x[0,1]) and finds the object space position of the
     * intersection of the line from the eye through the mouse
     * position at the near plane and the plane orthogonal to the view
     * direction through a given point of interest.
     *
     * \param mvp                 The modelview-projection matrix.
     * \param mvp_inv             The inverse of the modelview-projection matrix.
     * \param center_of_interest  The center of interest point in object space coordinates.
     * \param mouse_position      The mouse position normalized to [0,1]x[0,1]
     * \returns                   The object space position of the intersection.
     *
     * \author Christopher Dyken <christopher.dyken@sintef.no>
     */
    inline Vec3f
    mousePosOnInterestPlaneAsObjectCoords( const Mat4f& mvp,
                                           const Mat4f& mvp_inv,
                                           const Vec3f& center_of_interest,
                                           const Vec2f& mouse_position )
    {
        // First, find ndc-depth of center of interest plane
        float ndc_coi_z = objectCoordsAsNormalizedDeviceCoord( mvp,
                                                               center_of_interest )[2];
        
        // Then build ndc mouse pos at this depth and project back to object space,
        return normalizedDeviceCoordsAsObjectCoords( mvp_inv,
                                                     Vec3f( mouse_position[0],
                                                            mouse_position[1],
                                                            ndc_coi_z ) );
    }

  
  /**
   * Returns the interest plane from a modelViewProjection matrix
   * and a center of interest. 
   */
  inline HPlane3f getInterestPlane( const Mat4f &mvp, const Vec3f &coi)
  {
#ifndef NVCC
    HPlane3f interestPlane(Vec4f(0.f, 0.f, -1.f, 1.f) * mvp);
#else
    Vec4f temp = Vec4f(0.f, 0.f, -1.f, 1.f) * mvp;
    HPlane3f interestPlane;
    interestPlane.normal_[0] = temp[0];
    interestPlane.normal_[1] = temp[1];
    interestPlane.normal_[2] = temp[2];
    interestPlane.offset_ = temp[3];
#endif
    moveTo(interestPlane, coi);
    return interestPlane;
  }

  /**
   * Returns the two object space planes that represents the line
   * corresponding to a norm device coordinate.
   */
  inline void getPlanesFromNormDevCoords(HPlane3f &horizontal, HPlane3f &vertical, const Mat4f &mvp, const Vec2f &nd)
  {
    horizontal = Vec4f(1.f, 0.f, 0.f, -nd[0]) * mvp;
    vertical = Vec4f(0.f, -1.f, 0.f, nd[1]) * mvp;
  }

  /**
   * Returns a plane in object space that corresponds to a norm 
   * device coordinate.
   */
  inline Vec3f getPointOnPlaneFromNormDevCoords(const Mat4f &mvp, const HPlane3f &plane, const Vec2f &nd)
  {
    HPlane3f horizontal, vertical;
    getPlanesFromNormDevCoords(horizontal, vertical, mvp, nd);
    return intersection(horizontal, vertical, plane);
  }

  inline HPlane3f getNearPlane(const Mat4f mvp, float z_near = 1.0f)
  {
    return Vec4f(0.f, 0.f, -1.f, z_near) * mvp;
  }

  inline HPlane3f getFarPlane(const Mat4f mvp, float z_far = -1.0)
  {
    return Vec4f(0.f, 0.f, 1.f, -z_far) * mvp;
  }

  inline HPlane3f getLeftPlane(const Mat4f mvp, float x_left = -1.f)
  {
    return Vec4f(1.f, 0.f, 0.f, -x_left) * mvp;
  }

  inline HPlane3f getRightPlane(const Mat4f mvp, float x_right = 1.f)
  {
    return Vec4f(-1.f, 0.f, 0.f, x_right) * mvp;
  }

  inline HPlane3f getBottomPlane(const Mat4f mvp, float y_bottom = -1.f)
  {
    return Vec4f(0.f, 0.f, 1.f, -y_bottom) * mvp;
  }

  inline HPlane3f getTopPlane(const Mat4f mvp, float y_top = 1.f)
  {
    return Vec4f(0.f, 0.f, 1.f, -y_top) * mvp;
  }

  inline Ray3f getFrustumRayFromNormDevCoords(const Mat4f &mvp, const Vec2f nd)
  {
    HPlane3f yz = getLeftPlane(mvp, nd[0]);
    HPlane3f xz = getTopPlane(mvp, nd[1]);
    HPlane3f near = getNearPlane(mvp);
    HPlane3f far = getFarPlane(mvp);

    Vec3f p1 = intersection(yz, xz, near);
    Vec3f p2 = intersection(yz, xz, far);

    return fromEndpoints(p1, p2);
  }

  inline Frustum getFrustumFromNormDevRectangle(const Mat4f mvp, const Vec2f &ll, const Vec2f &ur)
  {
    HPlane3f lp = getLeftPlane(mvp, ll[0]);
    HPlane3f rp = getRightPlane(mvp, ur[0]);
    HPlane3f bp = getBottomPlane(mvp, ll[1]);
    HPlane3f tp = getTopPlane(mvp, ur[1]);
    HPlane3f np = getNearPlane(mvp);
    HPlane3f fp = getFarPlane(mvp);
    return Frustum(lp, rp, bp, tp, np, fp);
  }

  /**
   * Maps a pixel window coordinate to norm device pixel centers
   */
  inline Vec2f normDevFromWinCoords(const Vec2f &win_size, const Vec2f &wincoords)
  {
    return Vec2f( 2.f * (wincoords[0]+0.5f)/win_size[0] -1.f,
		  -2.f * (wincoords[1]+0.5f)/win_size[1] + 1.f);
  }


}//end namespaces
}


#endif
